﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;
using RegexMatch = System.Text.RegularExpressions.Match;

using Yorrick.FlickrMetadataSynchr.Properties;
using Yorrick.FlickrMetadataSynchr.Data;

namespace Yorrick.FlickrMetadataSynchr.Flickr
{
    internal static class DownloadedPictureFilenameHelper
    {
        #region Private constants
        /// <summary>
        /// The maximum number of characters in a filename with path.
        /// </summary>
        private const int _MaximumFilenameAndPathLength = 259;

        /// <summary>
        /// Regex matching the number suffix in a filename.
        /// </summary>
        private static readonly Regex _NumberSuffixRegex = new Regex(@" \([0-9]+\)$");
        #endregion

        #region Public methods
        /// <summary>
        /// Generates and returns a unique filename to use for a picture that is downloaded from Flickr.
        /// </summary>
        /// <param name="localFolder">The local folder in which the filename has to be unique.</param>
        /// <param name="metadata">The metadata for the picture on Flickr.</param>
        /// <returns>The filename to use for the downloaded file.</returns>
        /// <remarks>This method creates a filename based on the title of the picture on Flickr. Suffixes 
        /// are added if needed for uniqueness.</remarks>
        public static string GetFilenameForDownload(string localFolder, FlickrPictureMetadata metadata)
        {
            int maxFilenameLength = _MaximumFilenameAndPathLength - localFolder.Length - 1;

            string filenameWithoutPath = GetFilenameFromMetadata(maxFilenameLength, metadata);

            string filenameWithPath = Path.Combine(localFolder, filenameWithoutPath);

            string uniqueFilenameWithPath = EnsureUniqueFilename(filenameWithPath);

            return uniqueFilenameWithPath;
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Ensures that the specified filename is made unique if a file with that name already exists.
        /// </summary>
        /// <param name="filenameWithPath">The filename to check.</param>
        /// <returns>The filename that is unique.</returns>
        /// <remarks>If the file does not exist already, the filename is returned as-is. If it does
        /// exist a suffix like (1), (2), etc. is added to the file to make it unique.</remarks>
        private static string EnsureUniqueFilename(string filenameWithPath)
        {
            int counter = 1;

            while (true)
            {
                bool fileExists = File.Exists(filenameWithPath);
                if (!fileExists)
                {
                    return filenameWithPath;
                }

                filenameWithPath = PutCounterIntoFilename(filenameWithPath, counter);

                counter++;
            }
        }

        /// <summary>
        /// Puts the counter as suffix like (1), (2), etc. to the filename.
        /// </summary>
        /// <param name="filenameWithPath">The filename with path.</param>
        /// <param name="counter">The counter to use.</param>
        /// <returns></returns>
        /// <remarks>This method respects the maximum length for a filepath on Windows.</remarks>
        private static string PutCounterIntoFilename(string filenameWithPath, int counter)
        {
            FileInfo fileInfo = new FileInfo(filenameWithPath);

            string extension = fileInfo.Extension;
            string path = fileInfo.DirectoryName;
            string filenameWithExtension = fileInfo.Name;
            string filenameWithoutExtension = filenameWithExtension.Substring(0, filenameWithExtension.Length - extension.Length);

            RegexMatch match = _NumberSuffixRegex.Match(filenameWithoutExtension);

            if (match.Success)
            {
                filenameWithoutExtension = filenameWithoutExtension.Substring(0, match.Index);
                filenameWithPath = Path.Combine(path, filenameWithoutExtension + extension);
            }

            string counterSuffix = String.Format(CultureInfo.InvariantCulture, " ({0})", counter);

            int pathLengthOverflow = (filenameWithPath.Length + counterSuffix.Length) - _MaximumFilenameAndPathLength;

            if (pathLengthOverflow > 0)
            {
                int reducedFilenameWithoutExtensionLength = filenameWithoutExtension.Length - pathLengthOverflow;

                if (reducedFilenameWithoutExtensionLength <= 0)
                {
                    throw new ArgumentException(Messages.FolderPathTooLong);
                }

                filenameWithoutExtension = filenameWithoutExtension.Substring(0, reducedFilenameWithoutExtensionLength);
            }

            string newFilenameWithExtension = filenameWithoutExtension + counterSuffix + extension;

            string newFilenameWithPath = Path.Combine(path, newFilenameWithExtension);

            return newFilenameWithPath;
        }

        /// <summary>
        /// Generates and returns a filename to use for a picture that is downloaded from Flickr by using the title from the metadata.
        /// </summary>
        /// <param name="maxFilenameLength">The maximum number of characters for the filename (without path).</param>
        /// <param name="metadata">The metadata for the picture on Flickr.</param>
        /// <returns>The filename that can potentially be used for the downloaded file.</returns>
        /// <remarks>This method does not ensure uniqueness of the generated filename. If the picture has no title on Flickr, the Flickr
        /// id is used instead.</remarks>
        private static string GetFilenameFromMetadata(int maxFilenameLength, FlickrPictureMetadata metadata)
        {
            string flickrFilename = GetFileNameFromUrl(metadata.OriginalUrl);

            string flickrExtension = new FileInfo(flickrFilename).Extension;

            int maxFilenameLengthWithoutExtension = Math.Max(0, maxFilenameLength - flickrExtension.Length);

            if (maxFilenameLengthWithoutExtension == 0)
            {
                throw new ArgumentException(Messages.FolderPathTooLong);
            }

            string potentialFilename = metadata.Title.Trim();

            // If the picture has no title use the Flickr id instead.
            if (potentialFilename.Length == 0)
            {
                potentialFilename = metadata.FlickrId;
            }

            int lengthToUse = Math.Min(potentialFilename.Length, maxFilenameLengthWithoutExtension);

            string filenameFromMetadata = potentialFilename.Substring(0, lengthToUse) + flickrExtension;

            return SanitizeFilename(filenameFromMetadata);
        }

        /// <summary>
        /// Sanitizes the specified filename by replacing all disallowed characters by dashes.
        /// </summary>
        /// <param name="filenameToSanitize">The filename to sanitize.</param>
        /// <returns>The sanitized filename.</returns>
        private static string SanitizeFilename(string filenameToSanitize)
        {
            string sanitizedFilename =
                filenameToSanitize
                    .Replace('\\', '-')
                    .Replace('/', '-')
                    .Replace('|', '-')
                    .Replace(':', '-')
                    .Replace('<', '-')
                    .Replace('>', '-')
                    .Replace('*', '-')
                    .Replace('"', '\'')
                    .Replace("?", "")
                    .Replace("!", "")
                    .Trim();

            return sanitizedFilename;
        }

        /// <summary>
        /// Gets the filename from the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <returns>The filename from the specified URL.</returns>
        private static string GetFileNameFromUrl(string url)
        {
            int lastSlashIndex = url.LastIndexOf('/');
            if (lastSlashIndex == -1)
            {
                return null;
            }
            else
            {
                return url.Substring(lastSlashIndex + 1);
            }
        }
        #endregion
    }
}
